/**
 * Copyright 2013 Alexey Ragozin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.gridkit.jvmtool.cmd;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.management.MBeanServerConnection;

import org.gridkit.jvmtool.GlobHelper;
import org.gridkit.jvmtool.JmxConnectionInfo;
import org.gridkit.jvmtool.MBeanCpuUsageReporter;
import org.gridkit.jvmtool.PerfCounterGcCpuUsageMonitor;
import org.gridkit.jvmtool.PerfCounterSafePointMonitor;
import org.gridkit.jvmtool.cli.CommandLauncher;
import org.gridkit.jvmtool.cli.TimeIntervalConverter;
import org.gridkit.jvmtool.cli.CommandLauncher.CmdRef;

import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.beust.jcommander.ParametersDelegate;

/**
 * Thread top command.
 *  
 * @author Alexey Ragozin (alexey.ragozin@gmail.com)
 */
public class ThreadTopCmd implements CmdRef {

	@Override
	public String getCommandName() {
		return "ttop";
	}

	@Override
	public Runnable newCommand(CommandLauncher host) {
		return new TTop(host);
	}

	@Parameters(commandDescription = "[Thread Top] Displays threads from JVM process")
	public static class TTop implements Runnable {

		@ParametersDelegate
		private CommandLauncher host;
		
		@Parameter(names = {"-ri", "--report-interval"}, converter = TimeIntervalConverter.class, description = "Interval between CPU usage reports")
		private long reportIntervalMS = TimeUnit.SECONDS.toMillis(10);

		@Parameter(names = {"-si", "--sampler-interval"}, converter = TimeIntervalConverter.class, description = "Interval between polling MBeans")
		private long samplerIntervalMS = 500;
		
		@Parameter(names = {"-n", "--top-number"}, description = "Number of threads to show")
		private int topNumber = 20;
		
		@Parameter(names = {"-o", "--order"}, variableArity = true, description = "Sort order. Value tags: CPU, USER, SYS, ALLOC, NAME")
		private List<String> sortOrder = new ArrayList<String>(Arrays.asList("CPU"));
		
		@Parameter(names = {"-f", "--filter"}, description = "Wild card expression to filter threads by name")
		private String threadFilter;
		
		@ParametersDelegate
		private JmxConnectionInfo connInfo;
		
		public TTop(CommandLauncher host) {
			this.host = host;
			this.connInfo = new JmxConnectionInfo(host);
		}

		@Override
		public void run() {
			
			try {
				MBeanServerConnection mserver = connInfo.getMServer();
				
				final MBeanCpuUsageReporter tmon = new MBeanCpuUsageReporter(mserver);
				if (connInfo.getPID() != null) {
				    try {
    				    long pid = connInfo.getPID();
    				    PerfCounterGcCpuUsageMonitor pm = new PerfCounterGcCpuUsageMonitor(pid);
    				    if (pm.isAvailable()) {
    				        tmon.setGcCpuUsageMonitor(pm);
    				    }
    				    PerfCounterSafePointMonitor sm = new PerfCounterSafePointMonitor(pid);
    				    if (sm.isAvailable()) {
    				        tmon.setSafePointMonitor(sm);
    				    }
//    				    NativeThreadMonitor tm = PlatformProcessInfoProvider.createMonitor(pid);
//    				    if (tm != null) {
//    				        tmon.setNativeThreadMonitor(tm);
//    				    }
				    }
				    catch(Exception e) {
				        // ignore
				    }
				}
				
				tmon.setTopLimit(topNumber);
				
				if (threadFilter != null) {
					tmon.setThreadFilter(GlobHelper.translate(threadFilter, "\0"));
				}
				
				if (sortOrder != null) {
					Collections.reverse(sortOrder);
					for(String tag: sortOrder) {
						if ("SYS".equals(tag)) {
							tmon.sortBySysCpu();
						}
						else if ("USER".equals(tag)){
							tmon.sortByUserCpu();
						}
						else if ("CPU".equals(tag)){
							tmon.sortByTotalCpu();
						}
						else if ("ALLOC".equals(tag)){
							tmon.sortByAllocRate();
						}
						else if ("NAME".equals(tag)){
							tmon.sortByThreadName();
						}
						else {
							host.failAndPrintUsage("Invalid order option '" + tag + "'");
						}
					}
				}
				
				long deadline = System.currentTimeMillis() + Math.min(reportIntervalMS, 10 * samplerIntervalMS);
				tmon.report();
				System.out.println("Monitoring threads ...");
				while(true) {
					while(System.currentTimeMillis() < deadline) {
						Thread.sleep(samplerIntervalMS);
						tmon.probe();
					}
					deadline += reportIntervalMS;
					System.out.println();
					System.out.println(tmon.report());
					System.out.println();
					if (System.in.available() > 0) {
						return;
					}
				}
			} catch (Exception e) {
				host.fail("Unexpected error: " + e.toString(), e);
			}			
		}
	}
}
